package com.example.bigfile.service;

import com.example.bigfile.common.exception.BusinessException;
import com.example.bigfile.entity.MultipartFileParam;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.UUID;

/**
 * @author Wangx
 * @Title:
 * @Package
 * @Description:
 * @date 2022/9/23 17:19
 */
@Service
public class FileUploadService {

    /**
     * 获取已上传的块
     * @return
     */
    public int[] getUploaded(MultipartFileParam param, String fileName, String filePath) throws IOException {
        File confFile = new File(filePath,fileName+".conf");
        RandomAccessFile confAccessFile = new RandomAccessFile(confFile,"rw");
        int[] uploaded = {};
        byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
        confAccessFile.close();
        for(int i = 0; i<completeStatusList.length; i++){
            if(completeStatusList[i]==Byte.MAX_VALUE){
                uploaded= Arrays.copyOf(uploaded, uploaded.length+1);
                uploaded[uploaded.length-1] = i;
            }
        }
        return uploaded;
    }

    public String chunkUploadByMappedByteBuffer(MultipartFileParam param, String filePath) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        if(param.getTaskId() == null || "".equals(param.getTaskId())){
            param.setTaskId(UUID.randomUUID().toString());
        }
        /**
         *
         * 1：创建临时文件，和源文件一个路径
         * 2：如果文件路径不存在重新创建
         */
        String fileName = param.getFile().getOriginalFilename();
        String tempFileName = param.getTaskId() + fileName.substring(fileName.lastIndexOf(".")) + "_tmp";
        File fileDir = new File(filePath);
        if(!fileDir.exists()){
            fileDir.mkdirs();
        }
        File tempFile = new File(filePath,tempFileName);
        //第一步
        RandomAccessFile raf = new RandomAccessFile(tempFile,"rw");
        //第二步
        //FileChannel是连接到文件的通道。 使用文件通道，您可以从文件读取数据，并将数据写入文件。FileChannel不能设置为非阻塞模式。 它总是以阻止模式运行。
        FileChannel fileChannel = raf.getChannel();
        //第三步 计算偏移量
        long position = (param.getChunkNumber()-1) * param.getChunkSize();
        //第四步
        byte[] fileData = param.getFile().getBytes();
        //第五步
        long end=position+fileData.length-1;
        fileChannel.position(position);
        fileChannel.write(ByteBuffer.wrap(fileData));
        fileChannel.force(true);
        fileChannel.close();
        raf.close();
        //第八步
        boolean isComplete = checkUploadStatus(param,param.getTaskId(),filePath);
        if(isComplete){
            //重命名文件，然后校验MD5文件是否一致
            FileInputStream fis = null;
            String md5 = "";
            try{
                fis = new FileInputStream(tempFile.getPath());
                md5= DigestUtils.md5DigestAsHex(fis);
            }catch (IOException e){
                e.printStackTrace();
            }finally {
                if(fis!=null){
                    fis.close();
                }
            }
            renameFile(tempFile,fileName);
            if(StringUtils.isNotBlank(md5) && !md5.equals(param.getIdentifier())){
                //不是同一文件抛出异常
                throw new BusinessException("不是同一文件");
            }
        }
        return param.getTaskId();
    }
    /**
     * 检查文件上传进度
     * @return
     */
    public boolean checkUploadStatus(MultipartFileParam param,String fileName,String filePath) throws IOException {
        File confFile = new File(filePath,fileName+".conf");
        RandomAccessFile confAccessFile = new RandomAccessFile(confFile,"rw");
        //设置文件长度
        confAccessFile.setLength(param.getTotalChunks());
        //设置起始偏移量
        confAccessFile.seek(param.getChunkNumber()-1);
        //将指定的一个字节写入文件中 127，
        confAccessFile.write(Byte.MAX_VALUE);
        byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
        confAccessFile.close();//不关闭会造成无法占用
        //这一段逻辑有点复杂，看的时候思考了好久，创建conf文件文件长度为总分片数，每上传一个分块即向conf文件中写入一个127，那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127
        for(int i = 0; i<completeStatusList.length; i++){
            if(completeStatusList[i]!=Byte.MAX_VALUE){
                return false;
            }
        }
        //如果全部文件上传完成，删除conf文件
        confFile.delete();
        return true;
    }


    /**
     * 文件重命名
     * @param toBeRenamed   将要修改名字的文件
     * @param toFileNewName 新的名字
     * @return
     */
    public void renameFile(File toBeRenamed, String toFileNewName) {
        //检查要重命名的文件是否存在，是否是文件
        if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
            System.out.println("文件不存在");
            return;
        }
        String p = toBeRenamed.getParent();
        File newFile = new File(p + File.separatorChar + toFileNewName);
        //修改文件名
        toBeRenamed.renameTo(newFile);
    }
}
